/**
 * @file
 * @brief RTD：XWDS：SPI
 * @author
 * + 隐星魂 (Roy.Sun) <https://xwos.tech>
 * @copyright
 * + (c) 2015 隐星魂 (Roy.Sun) <https://xwos.tech>
 * > Licensed under the Apache License, Version 2.0 (the "License");
 * > you may not use this file except in compliance with the License.
 * > You may obtain a copy of the License at
 * >
 * >         http://www.apache.org/licenses/LICENSE-2.0
 * >
 * > Unless required by applicable law or agreed to in writing, software
 * > distributed under the License is distributed on an "AS IS" BASIS,
 * > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * > See the License for the specific language governing permissions and
 * > limitations under the License.
 */

#include "board/std.h"
#include <string.h>
#include <xwos/osal/time.h>
#include <xwos/osal/sync/cond.h>
#include <xwos/osal/lock/spinlock.h>
#include <xwcd/ds/spi/master.h>
#include "Lpspi_Ip.h"

struct rtdxwds_lpspim_driver_data {
        xwu8_t instance;
        const Lpspi_Ip_ConfigType * cfg;
        struct {
                struct xwos_splk lock;
                struct xwos_cond completion;
                xwer_t rc;
        } xfer;
};

static
xwer_t rtdxwds_lpspim_drv_probe(struct xwds_device * dev);

static
xwer_t rtdxwds_lpspim_drv_remove(struct xwds_device * dev);

static
xwer_t rtdxwds_lpspim_drv_start(struct xwds_device * dev);

static
xwer_t rtdxwds_lpspim_drv_stop(struct xwds_device * dev);

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t rtdxwds_lpspim_drv_suspend(struct xwds_device * dev);

static
xwer_t rtdxwds_lpspim_drv_resume(struct xwds_device * dev);
#endif

static
xwer_t rtdxwds_lpspim_drv_xfer(struct xwds_spim * spim, xwid_t cfgid,
                               const xwu8_t txd[], xwu8_t * rxb,
                               xwsz_t * size, xwtm_t to);

static
xwer_t rtdxwds_lpspim_drv_abort(struct xwds_spim * spim, xwtm_t to);

static
void rtdxwds_lpspim_callback(uint8 instance, Lpspi_Ip_EventType event);

struct xwds_spim_driver rtdxwds_lpspim_drv = {
        .base = {
                .name = "rtdxwds.lpspim",
                .probe = rtdxwds_lpspim_drv_probe,
                .remove = rtdxwds_lpspim_drv_remove,
                .start = rtdxwds_lpspim_drv_start,
                .stop =  rtdxwds_lpspim_drv_stop,
#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
                .suspend = rtdxwds_lpspim_drv_suspend,
                .resume =  rtdxwds_lpspim_drv_resume,
#endif
        },
        .xfer = rtdxwds_lpspim_drv_xfer,
        .abort = rtdxwds_lpspim_drv_abort,
};

static
xwer_t rtdxwds_lpspim_drv_probe(struct xwds_device * dev)
{
        struct xwds_spim * spim;
        struct rtdxwds_lpspim_driver_data * drvdata;

        spim = xwds_cast(struct xwds_spim *, dev);
        drvdata = spim->dev.data;
        xwos_splk_init(&drvdata->xfer.lock);
        xwos_cond_init(&drvdata->xfer.completion);
        return XWOK;
}

static
xwer_t rtdxwds_lpspim_drv_remove(struct xwds_device * dev)
{
        struct xwds_spim * spim;
        struct rtdxwds_lpspim_driver_data * drvdata;

        spim = xwds_cast(struct xwds_spim *, dev);
        drvdata = spim->dev.data;
        xwos_cond_fini(&drvdata->xfer.completion);
        return XWOK;
}

static
xwer_t rtdxwds_lpspim_drv_start(struct xwds_device * dev)
{
        struct xwds_spim * spim;
        struct rtdxwds_lpspim_driver_data * drvdata;
        Lpspi_Ip_StatusType rtdrc;
        xwer_t rc;

        spim = xwds_cast(struct xwds_spim *, dev);
        drvdata = spim->dev.data;
        rtdrc = Lpspi_Ip_Init(drvdata->cfg);
        if (LPSPI_IP_STATUS_SUCCESS == rtdrc) {
                Lpspi_Ip_UpdateTransferMode(drvdata->instance, LPSPI_IP_INTERRUPT);
                rc = XWOK;
        } else {
                rc = -EINVAL;
        }
        return rc;
}

static
xwer_t rtdxwds_lpspim_drv_stop(struct xwds_device * dev)
{
        struct xwds_spim * spim;
        struct rtdxwds_lpspim_driver_data * drvdata;
        Lpspi_Ip_StatusType rtdrc;
        xwer_t rc;

        spim = xwds_cast(struct xwds_spim *, dev);
        drvdata = spim->dev.data;
        rtdrc = Lpspi_Ip_DeInit(drvdata->instance);
        if (LPSPI_IP_STATUS_SUCCESS == rtdrc) {
                rc = XWOK;
        } else {
                rc = -EINVAL;
        }
        return rc;
}

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t rtdxwds_lpspim_drv_suspend(struct xwds_device * dev)
{
        return rtdxwds_lpspim_drv_stop(dev);
}

static
xwer_t rtdxwds_lpspim_drv_resume(struct xwds_device * dev)
{
        return rtdxwds_lpspim_drv_start(dev);
}
#endif

static
xwer_t rtdxwds_lpspim_drv_xfer(struct xwds_spim * spim, xwid_t cfgid,
                               const xwu8_t txd[], xwu8_t * rxb,
                               xwsz_t * size, xwtm_t to)
{
        struct rtdxwds_lpspim_driver_data * drvdata;
        const Lpspi_Ip_ExternalDeviceType ** buscfg;
        const Lpspi_Ip_ExternalDeviceType * peri;
        xwsz_t xfsz;
        xwreg_t cpuirq;
        union xwos_ulock lock;
        xwsq_t lkst;
        Lpspi_Ip_StatusType rtdrc;
        xwer_t rc;

        drvdata = spim->dev.data;
        buscfg = spim->buscfg;
        peri = buscfg[cfgid];
        lock.osal.splk = &drvdata->xfer.lock;
        xfsz = *size;

        xwos_splk_lock_cpuirqsv(&drvdata->xfer.lock, &cpuirq);
        drvdata->xfer.rc = -EBUSY;
        rtdrc = Lpspi_Ip_AsyncTransmit(peri, (xwu8_t *)txd, rxb, xfsz,
                                       rtdxwds_lpspim_callback);
        if (LPSPI_IP_STATUS_SUCCESS != rtdrc) {
                rc = -EIO;
                xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
                goto err_transmit;
        }
        rc = xwos_cond_wait_to(&drvdata->xfer.completion, lock, XWOS_LK_SPLK,
                               NULL, to, &lkst);
        if (XWOK == rc) {
                XWOS_BUG_ON((xwsq_t)XWOS_LKST_UNLOCKED == lkst);
                rc = drvdata->xfer.rc;
                xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
        } else {
                if ((xwsq_t)XWOS_LKST_LOCKED == lkst) {
                        xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
                } else {
                        xwos_cpuirq_restore_lc(cpuirq);
                }
        }

err_transmit:
        return rc;
}

static
xwer_t rtdxwds_lpspim_drv_abort(struct xwds_spim * spim, xwtm_t to)
{
        struct rtdxwds_lpspim_driver_data * drvdata;
        xwreg_t cpuirq;

        XWOS_UNUSED(to);
        drvdata = spim->dev.data;
        Lpspi_Ip_Cancel(drvdata->instance);
        xwos_splk_lock_cpuirqsv(&drvdata->xfer.lock, &cpuirq);
        if (-EBUSY == drvdata->xfer.rc) {
                drvdata->xfer.rc = -ECANCELED;
                xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
                xwos_cond_broadcast(&drvdata->xfer.completion);
        } else {
                xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
        }
        return XWOK;
}

/******** ******** lpspi0m ******** ********/
struct rtdxwds_lpspim_driver_data rtdxwds_lpspi0m_drvdata = {
        .instance = 0,
        .cfg = &Lpspi_Ip_PhyUnitConfig_SpiPhyUnit_0_Instance_0_BOARD_InitPeripherals,
};

const Lpspi_Ip_ExternalDeviceType * const rtdxwds_lpspi0m_buscfg[] = {
        [0] = &Lpspi_Ip_DeviceAttributes_SpiPeripheral_W25QXX_Instance_0_BOARD_InitPeripherals,
};

struct xwds_spim rtdxwds_lpspi0m = {
        /* attributes */
        .dev = {
                .name = "rtdxwds.lpspi.m",
                .id = 0,
                .resources = NULL,
                .drv = xwds_cast(struct xwds_driver *, &rtdxwds_lpspim_drv),
                .data = (void *)&rtdxwds_lpspi0m_drvdata,
        },
        .buscfg = (void *)rtdxwds_lpspi0m_buscfg,
        .buscfg_num = xw_array_size(rtdxwds_lpspi0m_buscfg),
};


struct xwds_spim * const rtdxwds_lpspim_devices[] = {
        [0] = &rtdxwds_lpspi0m,
};

/******** ******** Callback ******** ********/
void rtdxwds_lpspim_callback(uint8 instance, Lpspi_Ip_EventType event)
{
        struct xwds_spim * spim;
        struct rtdxwds_lpspim_driver_data * drvdata;
        xwreg_t cpuirq;

        spim = rtdxwds_lpspim_devices[instance];
        drvdata = spim->dev.data;

        xwos_splk_lock_cpuirqsv(&drvdata->xfer.lock, &cpuirq);
        if (-EBUSY == drvdata->xfer.rc) {
                if (LPSPI_IP_EVENT_END_TRANSFER == event) {
                        drvdata->xfer.rc = XWOK;
                } else {
                        drvdata->xfer.rc = -EIO;
                }
                xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
                xwos_cond_broadcast(&drvdata->xfer.completion);
        } else {
                xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
        }
}
